package lu.uni.adtool.ui;

import java.awt.Color;
import java.awt.Frame;

import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;

import lu.uni.adtool.Options;

import lu.uni.adtool.adtree.ADTNode;
import lu.uni.adtool.adtree.ADTreeForGui;
import lu.uni.adtool.adtree.ADTreeNode;
import lu.uni.adtool.adtree.Node;

import lu.uni.adtool.domains.Domain;
import lu.uni.adtool.domains.Evaluator;
import lu.uni.adtool.domains.ValueAssignement;

import lu.uni.adtool.domains.predefined.IntMinMax;

import lu.uni.adtool.domains.rings.Bool;
import lu.uni.adtool.domains.rings.BoundedInteger;
import lu.uni.adtool.domains.rings.RealG0;
import lu.uni.adtool.domains.rings.RealZeroOne;
import lu.uni.adtool.domains.rings.Ring;

/**
 * Canvas for drawing tree for specific domain.
 * 
 * @author Piot Kordy
 */
public class DomainCanvas<Type> extends ADTreeCanvas 
{
  static final long serialVersionUID = 712665011570251703L;
  transient private Evaluator<Type> evaluator;
  private Domain<Type> domain;
  private ValueAssignement<Type> valueAssPro;
  private ValueAssignement<Type> valueAssOpp;
  private boolean showLabels;
  private boolean showAllLabels;
  private InputDialog dialog;
  private boolean markEditable; 
  /**
   * Creates new instance.
   * 
   * @param newTree
   * @param domain
   * @param termView
   */
  public DomainCanvas(final ADTreeForGui newTree,
      final Domain<Type> domain,MainWindow mw)
  {
    super(newTree,mw);
    this.evaluator = new Evaluator<Type>(domain);
    this.valueAssPro = new ValueAssignement<Type>();
    this.valueAssOpp = new ValueAssignement<Type>();
    this.domain = domain;
    this.getMainWindow().setLastFocused(this);
    this.treeChanged();
    dialog = null;
    markEditable = true;
  }
  /**
   * Shows the dialog to edit a value in the tree.
   * 
   * @param node
   */
  public void editValue(ADTreeNode node)
  {
    if (node.getTerm().isEditable(domain)){
      String key = node.getTerm().getName();
      Type value = null;
      if(node.getType() == ADTreeNode.Type.PROPONENT){
        value = valueAssPro.get(key);
      }
      else {
        value = valueAssOpp.get(key);
      }
      if (value instanceof Bool){
        value = (Type)Bool.not((Bool)value);
      }
      else if (value instanceof RealG0){
        dialog = new RealG0Dialog(getMainWindow());
        value = (Type)(dialog.showInputDialog(value));
      }
      else if (value instanceof RealZeroOne){
        dialog = new RealZeroOneDialog(getMainWindow());
        value = (Type)(dialog.showInputDialog(value));
      }
      else if (value instanceof BoundedInteger){
        if(getDomain() instanceof IntMinMax){
          dialog = new BoundedIntegerInfDialog(getMainWindow());
        }
        else{
          dialog = new BoundedIntegerDialog(getMainWindow());
        }
        value = (Type)(dialog.showInputDialog(value));
      }
      if (value != null){
        putNewValue(node.getType() == ADTreeNode.Type.PROPONENT,key,value);
        valuesUpdated();
      }
    }

    SwingUtilities.invokeLater(new Runnable()
    {
      public void run()
      {
        // table.requestFocusInWindow();
        requestFocus();
      }
    });
  }
  /**
   * Assigns a new value to a node. 
   * 
   * @param key
   */
  public void putNewValue(boolean proponent, String key,Type value){
    if(proponent){
      valueAssPro.put(key,value);
    }
    else{
      valueAssOpp.put(key,value);
    }
  }

  public void refreshAllValues()
  {
    ValueAssignement<Type> proNew = new ValueAssignement<Type>();
    ValueAssignement<Type> oppNew = new ValueAssignement<Type>();
    refreshAllValues(tree.getRoot(true).getTerm(),proNew,oppNew);
    valueAssPro = proNew;
    valueAssOpp = oppNew;
  }

  private void refreshAllValues(final ADTNode node, final ValueAssignement proNew, final ValueAssignement oppNew)
  {
    if (node.getType() == ADTNode.Type.LEAFP){
      Ring value = (Ring)valueAssPro.get(node.getName());
      if (value == null){
        proNew.put(node.getName(), getDomain().getDefaultValue(true));
      }
      else{
        proNew.put(node.getName(),value);
      }
    }
    else if (node.getType() == ADTNode.Type.LEAFO){
      Ring value = (Ring)valueAssOpp.get(node.getName());
      if (value == null){
        oppNew.put(node.getName(), getDomain().getDefaultValue(false));
      }
      else{
        oppNew.put(node.getName(),value);
      }
    }
    else {
      if (node.getChildren() != null) {
        for(Node c:node.getChildren()){
          if (c != null) {
            refreshAllValues((ADTNode)c,proNew,oppNew);
          }
        }
      }
    }
  }

  public void valuesUpdated()
  {
    evaluator.reevaluate(tree.getRoot(true).getTerm(),valueAssPro,valueAssOpp);
    ValuationView valuationView = ((ValuationView)getMainWindow().getViews()[2].getComponent());
    if(valuationView.getCanvas()==this){
      valuationView.assignCanvas(this);
    }
    tree.updateAllSizes();
    this.repaint();
  }
  @Override
  protected void addListener()
  {
    listener = new DomainCanvasHandler<Type>(this);
    this.addMouseListener(listener);
    this.addMouseMotionListener(listener);
    this.addKeyListener(listener);
    this.addFocusListener(this);
  }
/**
 * Recalculate the positions of the nodes on the canvas when the 
 * size of some nodes have changed.
 * 
 */
  @Override
  public void sizeChanged(){
    recalculateLayout();
  }
/**
 * Recalculate the positions of the nodes on the canvas when the tree is
 * changed.
 * 
 */
  @Override
  public void treeChanged()
  {
    this.valueAssPro.setDefault(true,tree.getRoot(true).getTerm(),domain);
    this.valueAssOpp.setDefault(false,tree.getRoot(true).getTerm(),domain);
    this.evaluator.reevaluate(tree.getRoot(true).getTerm(), valueAssPro, valueAssOpp);
    this.refreshAllValues();
    ValuationView valuationView = ((ValuationView)getMainWindow().getViews()[2].getComponent());
    if(valuationView.getCanvas()==this){
      valuationView.assignCanvas(this);
    }
    tree.updateAllSizes();
  }
  /**
   * Gets the domain for this instance.
   *
   * @return The domain.
   */
  public Domain<Type> getDomain()
  {
    return this.domain;
  }

  /**
   * Sets the valueAssPro for this instance.
   *
   * @param newValueAssPro new value assignement.
   */
  public void setValueAssPro(ValueAssignement<Type> newValueAss)
  {
    this.valueAssPro = newValueAss;
    evaluator.reevaluate(tree.getRoot(true).getTerm(),valueAssPro,valueAssOpp);
  }

   /* Sets the valueAssOpp for this instance.
   *
   * @param newValueAssOpp new value assignement.
   */
  public void setValueAssOpp(ValueAssignement<Type> newValueAss)
  {
    this.valueAssOpp = newValueAss;
    evaluator.reevaluate(tree.getRoot(true).getTerm(),valueAssPro,valueAssOpp);
  }

  /**
   * Gets the valueAssPro for this instance.
   *
   * @return The valueAssPro.
   */
  public ValueAssignement<Type> getValueAssPro()
  {
    return this.valueAssPro;
  }
   /** Gets the valueAssOpp for this instance.
   *
   * @return The valueAssOpp.
   */
  public ValueAssignement<Type> getValueAssOpp()
  {
    return this.valueAssOpp;
  }

  /**
   * Sets whether or not this instance is showLabels.
   *
   * @param showLabels The showLabels.
   */
  public void setShowLabels(boolean showLabels)
  {
    this.showLabels = showLabels;
    tree.updateAllSizes();
    repaint();
  }

  /**
   * Sets whether or not this instance is showAllLabels.
   *
   * @param showAllLabels The showAllLabels.
   */
  public void setShowAllLabels(boolean showAllLabels)
  {
    this.showAllLabels = showAllLabels;
    tree.updateAllSizes();
    repaint();
  }

  /**
   * Determines if this instance is markEditable.
   *
   * @return The markEditable.
   */
  public boolean isMarkEditable()
  {
    return this.markEditable;
  }

  /**
   * Sets whether or not this instance is markEditable.
   *
   * @param markEditable The markEditable.
   */
  public void setMarkEditable(boolean markEditable)
  {
    this.markEditable = markEditable;
    repaint();
  }
  protected String getFillColorS(ADTreeNode node)
  {
    if (markEditable){
      if(node.getTerm().isEditable(getDomain())){
        return "editableFill";
      }
    }
    return super.getFillColorS(node);
  }

  protected Color getFillColor(ADTreeNode node)
  {
    if (markEditable){
      if(node.getTerm().isEditable(getDomain())){
        return Options.canv_EditableColor;
      }
    }
    return super.getFillColor(node);
  }

  /**
   * {@inheritDoc}
   * @see ADTreeCanvas#getLabel(ADTreeNode)
   */
  public String getLabel(ADTreeNode node)
  { 
    String result;
    if (node == null){
      return "Null node";
    }
    if (showLabels){
      result = node.getLabel()+"\n";
    }
    else{
      result = "";
    }
    Object value = null;
    if (evaluator !=null){
      ADTNode term = node.getTerm();
      if (term != null) {
        if ((term.getType() == ADTNode.Type.CP || term.getType() == ADTNode.Type.CO)
            && ((ADTNode)term.getChildren().elementAt(0)).getChildren().size()==0) {
          if (showAllLabels) {
            value = evaluator.getValue(node.getTerm());
            result += toUnicode((Ring) value) + "\n";
          }
          value = evaluator.getValue((ADTNode) term.getChildren().elementAt(0));
        }
        else {
          value = evaluator.getValue(node.getTerm());
        }
        result += toUnicode((Ring) value);
      }
    }
    return result;
  }
  /**
   * Converts value to unicode string.
   * 
   * @param value value
   * @return
   */
  private String toUnicode(Ring value)
  {
     if (value == null) {
       return "null";
     }
     else{
       return value.toUnicode();
     }
  }
}
